package it.infodea.tapestrydea.services.pageinfo.impl;

import it.infodea.tapestrydea.services.pageinfo.PageInfoService;
import it.infodea.tapestrydea.services.pageinfo.bean.PageInfoBean;
import it.infodea.tapestrydea.support.annotations.PageInfo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.tapestry5.Link;
import org.apache.tapestry5.internal.InternalConstants;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ClassNameLocator;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.PageRenderLinkSource;

public class PageInfoServiceImpl implements PageInfoService {

	public final Collection<PageInfoBean> pageInfoCollection;

	public final Map<String, PageInfoBean> pageInfoMap;

	public final Map<String, Collection<PageInfoBean>> pageInfoCollMap;

	public final Map<String, Collection<String>> categoryMap;

	public final String searchPackage;

	public final PageRenderLinkSource linkFactory;

	private final ComponentClassResolver componentClassResolver;
	
	

	public PageInfoServiceImpl(Collection<PageInfoBean> additionalPageInfoCollection, ClassNameLocator classNameLocator,
			PageRenderLinkSource linkFactory, @Inject @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM) String appRootPackage,
			ComponentClassResolver componentClassResolver)
			throws ClassNotFoundException {
		this.componentClassResolver = componentClassResolver;
		pageInfoCollection = new ArrayList<PageInfoBean>();
		pageInfoCollMap = new HashMap<String, Collection<PageInfoBean>>();
		categoryMap = new HashMap<String, Collection<String>>();
		pageInfoMap = new HashMap<String, PageInfoBean>();
		searchPackage = appRootPackage + "." + InternalConstants.PAGES_SUBPACKAGE;
		this.linkFactory = linkFactory;
		Collection<String> locateClassNames = classNameLocator.locateClassNames(searchPackage);
		for (String className : locateClassNames) {
			Class<?> pageClass = Class.forName(className);
			PageInfo pageInfo = (PageInfo) pageClass.getAnnotation(PageInfo.class);
			if (pageInfo != null) {
				PageInfoBean pageInfoBean = new PageInfoBean(pageInfo, getPageLink(pageClass), getPageLabel(pageClass));
				managePageInfoBean(pageInfoBean);
				pageInfoMap.put(className, pageInfoBean);
			}
		}
		additionalPageInfoCollection.add(PageInfoBean.newCategoryLink(PageInfo.CATEGORY_ROOT, PageInfo.CATEGORY_ROOT));
		for (PageInfoBean pageInfoBean : additionalPageInfoCollection) {
			managePageInfoBean(pageInfoBean);
		}
	}

	public PageInfoBean getPageInfoBean(Class<?> pageClass) {
		return pageInfoMap.get(getPageClassName(pageClass));
	}

	public Collection<PageInfoBean> getPageInfoBean() {
		return pageInfoCollection;
	}

	public Collection<PageInfoBean> getPageInfoByCategory(String category) {
		if (category == null) {
			category = PageInfo.CATEGORY_ROOT;
		}
		return getNotNullCollection(pageInfoCollMap.get(category));
	}

	public Collection<String> getCategoriesByParent(String parentCategory) {
		if (parentCategory == null) {
			parentCategory = PageInfo.CATEGORY_ROOT;
		}
		return getNotNullCollection(categoryMap.get(parentCategory));
	}

	public String getPagePath(Class<?> pageClass) {
		PageInfoBean pageInfoBean = getPageInfoBean(pageClass);
		return (pageInfoBean == null) ? null : pageInfoBean.getPagePath();
	}

	private String getPageLabel(Class<?> pageClass) {
		if (pageClass == null) {
			return null;
		}
		return pageClass.getSimpleName();
	}

	private void managePageInfoBean(PageInfoBean pageInfoBean) {
		addPageInfoBean(pageInfoBean);
		mapPageInfoByCategory(pageInfoBean);
		mapCategoryChildren(pageInfoBean);
	}

	private void addPageInfoBean(PageInfoBean pageInfo) {
		pageInfoCollection.add(pageInfo);
	}

	private void mapPageInfoByCategory(PageInfoBean pageInfoBean) {
		String category = pageInfoBean.getCategory();
		Collection<PageInfoBean> pageInfoList = getPageInfoByCategory(category);
		pageInfoList.add(pageInfoBean);
		pageInfoCollMap.put(category, pageInfoList);
	}

	private void mapCategoryChildren(PageInfoBean pageInfoBean) {
		String category = pageInfoBean.getCategory();
		String parentCategory = pageInfoBean.getParentCategory();
		linkCategory(parentCategory, category);
	}

	private void linkCategory(String parentCategory, String category) {
		if (parentCategory == null || parentCategory.equals(category)) {
			return;
		}
		Collection<String> children = getCategoriesByParent(parentCategory);
		if(!children.contains(category)) children.add(category);
		categoryMap.put(parentCategory, children);
	}

	private <T> Collection<T> getNotNullCollection(Collection<T> collection) {
		return (collection == null) ? new ArrayList<T>() : collection;
	}

	private String getPageClassName(Class<?> pageClass) {
		return pageClass.getCanonicalName();
	}
	
	public Link createPageLink(String pageClassName, boolean override, Object... context) {
		return linkFactory.createPageRenderLinkWithContext(getPageLink(pageClassName), context);
	}

	public String getPageLink(Class<?> pageClass) {
		if (pageClass == null) {
			return null;
		}
		String pageName = getPageLink(pageClass.getName());
		pageName = componentClassResolver.canonicalizePageName(pageName);
		return pageName;
	}

	private String getPageLink(String pageClassName) {
		String dottedLink = pageClassName.substring(searchPackage.length() + 1);
		return dottedLink.replace('.', '/');
	}

}
